#pragma once
// 此版本可以实现自动二倍扩容
#include <iostream>
#include <poll.h>
#include <memory>
#include "Socket.hpp"
using namespace socket_ns;
using namespace std;
// select服务器可以以单进程同时处理多个用户的请求，这种策略就叫：多路转接。
// select服务器需要借助一个辅助数组，保存合法的fd，方便统一添加管理
class PollServer
{
    // fd_set是位图类型，直接获取此位图的大小*8，就是该位图的比特位大小，该位图中存在多少个比特位，select就可以监管几个fd
    // select有缺点：select只能监管有限个fd，因为fd_set位图有上限，大小是固定的
    int gnum = 2;                       // 数组容量
    const static int defaultfd = -1;    // 缺省文件描述符
    const static int gdefaultevent = 0; // events、revents的缺省

public:
    PollServer(uint16_t port)
        : _port(port),
          _listensock(make_unique<TcpSocket>()),
          _timeout(1000)
    {
        // 构建网络信息
        InetAddr addr("0.0.0.0", _port);
        // 获取listen套接字
        _listensock->BuildListenSocket(addr);
        _events = (struct pollfd *)malloc(sizeof(struct pollfd) * gnum);
        if (_events == nullptr)
        {
            LOG(ERROR, "malloc初始化数组错误\n");
            exit(-1);
        }
        LOG(INFO, "初始化数组完成---gnum:%d\n", gnum);
        // 初始化的时候，就需要将读的辅助数组进行初始化
        for (int i = 0; i < gnum; i++)
        {
            _events[i].fd = defaultfd;
            _events[i].events = gdefaultevent;
            _events[i].revents = gdefaultevent;
        }
        _events[0].fd = _listensock->SockFd();
        _events[0].events = POLLIN; // 表示关心读事件，listensock上有连接，就是读事件
    }
    // 处理listensockfd的工作-获取连接
    void AcceptClinet()
    {
        InetAddr clientaddr;                             // 客户端网络信息
        int sockfd = _listensock->Accepter(&clientaddr); // 在这里调用accept时，不会阻塞等待，因为select已经做了等待的操作了，这里直接会进行获取。
        // 返回文件描述符
        if (sockfd < 0)
        {
            return;
        }
        // 此时，获取的fd就是一个合法的fd
        LOG(DEBUG, "获取了一个新连接,sockfd:%d ,client ip:%s , client port:%d\n", sockfd, clientaddr.Ip().c_str(), clientaddr.Port());
        // 调用accept之后，会获取一个fd，应该将此新获取的fd交给select,让select去帮我们进行等待
        // 只需要将新获得的fd添加到_fd_array数组中，下次调用select时传递给它，让select帮我们去等待即可
        int pos = 0; // 从1位置开始去找缺省的fd位置
        for (; pos < gnum; pos++)
        {
            if (_events[pos].fd == defaultfd)
            {
                // 找到了
                break;
            }
        }
        if (pos == gnum)
        {
            // 此处应该进行扩容
            gnum = gnum * 2;
            _events = (struct pollfd *)realloc(_events, sizeof(struct pollfd) * gnum);
            if (_events == nullptr)
            {
                LOG(ERROR, "扩容失败\n");
                ::close(sockfd); // 只能将新获取的fd给关闭了,然后返回即可
                LOG(WARNING, "服务器满载-扩容失败\n");
                return;
            }
            LOG(INFO, "扩容成功 gnum:%d\n", gnum);
            // 初始化的时候，就需要将读的辅助数组进行初始化
            for (int i = pos; i < gnum; i++)
            {
                _events[i].fd = defaultfd;
                _events[i].events = gdefaultevent;
                _events[i].revents = gdefaultevent;
            }
            _events[pos].fd = sockfd;
            _events[pos].events = POLLIN;
            _events[pos].revents = gdefaultevent;
        }
        else
        {
            // 添加新的fd到_fd_array数组中
            _events[pos].fd = sockfd;
            _events[pos].events = POLLIN;
            _events[pos].revents = gdefaultevent;
            LOG(DEBUG, "fd:%d文件描述符已经被添加至读-辅助数组中了\n", sockfd);
        }
        LOG(DEBUG, "当前_fd_array[] fd-list:%s\n", RfdsToString().c_str());
    }
    // 处理sockfd的工作-进行读写通信
    void ServiceIO(int pos)
    {
        char buffer[1024];                                                // 接收缓冲区
        ssize_t n = recv(_events[pos].fd, buffer, sizeof(buffer) - 1, 0); // 此时，调用recv读取不会进行阻塞等待，因为等待的工作select已经做过了，此时此recv接口直接获取数据即可
        if (n > 0)
        {
            // 读取成功
            buffer[n] = '\0';
            cout << "server收到了一个信息 # " << buffer << endl;
            string echo_string = "[Server Echo] # ";
            echo_string += buffer;
            // 发送
            ::send(_events[pos].fd, echo_string.c_str(), echo_string.size(), 0);
        }
        else if (n == 0)
        {
            // 客户端退出了
            ::close(_events[pos].fd);
            LOG(DEBUG, "fd:%d 已经被关闭了\n", _events[pos].fd);
            // 在_fd_array数组中将关闭了的fd进行释放
            _events[pos].fd = defaultfd;
            _events[pos].events = gdefaultevent;
            _events[pos].revents = gdefaultevent;
            LOG(DEBUG, "当前_fd_array[] fd-list:%s\n", RfdsToString().c_str());
        }
        else
        {
            // 读取失败了
            ::close(_events[pos].fd);
            // 在_fd_array数组中将关闭了的fd进行释放
            LOG(DEBUG, "fd:%d 读取失败\n", _events[pos].fd);
            _events[pos].fd = defaultfd;
            _events[pos].events = gdefaultevent;
            _events[pos].revents = gdefaultevent;
            LOG(DEBUG, "当前_fd_array[] fd-list:%s\n", RfdsToString().c_str());
        }
    }

    // 处理rfds-读事件中就绪了的fd
    void HanderEvents()
    {
        for (int i = 0; i < gnum; i++)
        {
            // 非法fd
            if (_events[i].fd == defaultfd)
            {
                continue;
            }
            // 合法的fd
            int fd = _events[i].fd;
            short revents = _events[i].revents;
            // 读事件
            if (revents & POLLIN)
            {
                if (fd == _listensock->SockFd())
                {
                    // listensockfd
                    AcceptClinet();
                }
                else
                {
                    // sockfd
                    ServiceIO(i);
                }
            }
            // 写事件
            if (revents & POLLOUT)
            {
            }
        }
    }

    // 启动select服务
    void Loop()
    {
        while (true)
        {

            int n = poll(_events, gnum, -1);
            switch (n)
            {
            case -1:
            {
                // select出错
                LOG(ERROR, "poll函数出错\n");
                break;
            }
            case 0:
            {
                // select函数超时了
                LOG(INFO, "poll函数超时了,timeout:%d\n", _timeout);
                break;
            }
            default:
            {
                // 此时，select函数等待成功了，返回已经读就绪的文件描述符的个数
                LOG(DEBUG, "存在fd的读事件就绪了,poll返回值:%d\n", n);
                // 底层只要由一个fd读事件就绪了，select就会一直通知服务器，让服务器去处理
                // 因为fd_set是一个输入输出型参数，返回时，已经就绪了的fd就在rfds位图中
                // 处理rfds-读事件中就绪了的fd
                HanderEvents();
                break;
            }
            }
        }
    }
    // 将所有的合法的fd输出到字符串中
    string RfdsToString()
    {
        string fdstr;
        for (int i = 0; i < gnum; i++)
        {
            if (_events[i].fd == defaultfd)
            {
                continue;
            }
            if (_events[i].fd > 0)
            {
                fdstr += to_string(_events[i].fd);
                fdstr += " ";
            }
        }
        return fdstr;
    }
    ~PollServer()
    {
    }

private:
    uint16_t _port;                 // 端口号
    unique_ptr<Socket> _listensock; // 套接字指针
    struct pollfd *_events;         // 辅助数组-关心读事件
    int _timeout;                   // 等待策略
};